from typing import Dict, Union, Optional, Iterable
from enum import Enum

from .client import Client
from .consts import *
from .utils import enum_to_str, iterable_to_str
from ..exceptions import OkexParamsException

from .ccytype import CcyType
from .mgnmode import MgnMode

class TdMode(Enum):
    # 保证金模式
    ISOLATED = "isolated"
    CROSS = "cross"
    # 非保证金模式
    CASH = "cash"


class PosSide(Enum):
    LONG = "long"
    SHORT = "short"


class OrderType(Enum):
    # 市价单
    MARKET = "market"
    # 限价单
    LIMIT = "limit"
    # 只做maker单
    POST_ONLY = "post_only"
    # 全部成交或立即取消
    FOK = "fok"
    # 立即成交并取消剩余
    IOC = "ioc"
    # 单向止盈止损
    CONDITIONAL = "conditional"
    # 双向止盈止损
    OCO = "oco"
    # 计划委托
    TRIGGER = "trigger"
    # 移动止盈止损
    MOVE_ORDER_STOP = "move_order_stop"
    # 时间加权委托
    TWAP = "twap"


class TrgCCY(Enum):
    # 交易货币
    BASE_CCY = "base_ccy"
    # 计价货币
    QUOTE_CCY = "quote_ccy"


class Order(object):
    def __init__(self,
                 instId: str,
                 tdMode: Union[TdMode, str],
                 ordType: Union[OrderType, str],
                 sz: Union[float, int, str],
                 ccy: Optional[Union[CcyType, str]] = None,
                 clOrdId: Optional[str] = None,
                 tag: Optional[str] = None,
                 posSide: Optional[Union[PosSide, str]] = None,
                 reduceOnly: Optional[Union[str, bool]] = None,
                 tgtCcy: Optional[Union[TrgCCY, str]] = None,
                 px: float = None
                 ) -> None:
        super().__init__()
        self.px = px
        self.instId = instId
        self.tdMode = tdMode
        self.ordType = ordType
        self.sz = sz
        self.ccy = ccy
        self.clOrdId = clOrdId
        self.tag = tag
        self.posSide = posSide
        self.reduceOnly = reduceOnly
        self.tgtCcy = tgtCcy


class CancelOrder(object):
    def __init__(self, instId: str, ordId: Optional[str] = None, clOrdId: Optional[str] = None) -> None:
        super().__init__()
        self.instId = instId
        self.ordId = ordId
        self.clOrdId = clOrdId


class TradeAPI(Client):

    def __init__(self, api_key, api_secret_key, passphrase, use_server_time=False, test=False, first=False):
        Client.__init__(self, api_key, api_secret_key,
                        passphrase, use_server_time, test, first)

    def order(self, instId: str,
              tdMode: Union[TdMode, str],
              ordType: Union[OrderType, str],
              sz: Union[float, int, str],
              ccy: Optional[Union[CcyType, str]] = None,
              clOrdId: Optional[str] = None,
              tag: Optional[str] = None,
              side=None,
              posSide: Optional[Union[PosSide, str]] = None,
              px: Optional[Union[float, int, str]] = None,
              reduceOnly: Optional[Union[str, bool]] = None,
              tpTriggerPx: Optional[str] = None,  # 止盈触发价
              tpOrdPx: Optional[str] = None,  # 止盈委托价,-1执行市价止盈
              slTriggerPx: Optional[str] = None,  # 止损触发价
              slOrdPx: Optional[str] = None,  # 止损委托价,-1 市价止盈
              ) -> Dict:
        params = {}
        if instId is not None:
            params['instId'] = str(instId)
        if tdMode is not None:
            params['tdMode'] = enum_to_str(tdMode)
        if ordType is not None:
            params['ordType'] = enum_to_str(ordType)
        if sz is not None:
            params['sz'] = str(abs(sz))
            if sz >= 0:
                params['side'] = 'buy'
            else:
                params['side'] = 'sell'
        if ccy is not None:
            params['ccy'] = enum_to_str(ccy)
        if clOrdId is not None:
            params['clOrdId'] = str(clOrdId)
        if tag is not None:
            params['tag'] = str(tag)
        if posSide is not None:
            params['posSide'] = enum_to_str(posSide)
        if side is not None:
            params['side'] = str(side)
        if px is not None:
            params['px'] = str(px)
        if reduceOnly is not None:
            if isinstance(reduceOnly, bool):
                if reduceOnly:
                    params['reduceOnly'] = 'true'
                else:
                    params['reduceOnly'] = 'false'
            else:
                params['reduceOnly'] = str(reduceOnly)
        if tpTriggerPx is not None:
            params['tpTriggerPx'] = tpTriggerPx
        if tpOrdPx is not None:
            params['tpOrdPx'] = str(tpOrdPx)
        if slTriggerPx is not None:
            params['slTriggerPx'] = slTriggerPx
        if slOrdPx is not None:
            params['slOrdPx'] = str(slOrdPx)
        data = self._request_with_params(POST, ORDER, params)

        return data

    # 新接口,包装了止盈止损
    def order_new(self, instId: str,
                  tdMode: Union[TdMode, str],
                  ordType: Union[OrderType, str],
                  sz: Union[float, int, str],
                  ccy: Optional[Union[CcyType, str]] = None,
                  clOrdId: Optional[str] = None,
                  tag: Optional[str] = None,
                  side=None,
                  posSide: Optional[Union[PosSide, str]] = None,
                  px: Optional[Union[float, int, str]] = None,
                  reduceOnly: Optional[Union[str, bool]] = None,
                  attachAlgoOrds=None
                  ) -> Dict:
        params = {}
        if instId is not None:
            params['instId'] = str(instId)
        if tdMode is not None:
            params['tdMode'] = enum_to_str(tdMode)
        if ordType is not None:
            params['ordType'] = enum_to_str(ordType)
        if sz is not None:
            params['sz'] = str(abs(sz))
            if sz >= 0:
                params['side'] = 'buy'
            else:
                params['side'] = 'sell'
        if ccy is not None:
            params['ccy'] = enum_to_str(ccy)
        if clOrdId is not None:
            params['clOrdId'] = str(clOrdId)
        if tag is not None:
            params['tag'] = str(tag)
        if posSide is not None:
            params['posSide'] = enum_to_str(posSide)
        if side is not None:
            params['side'] = str(side)
        if px is not None:
            params['px'] = str(px)
        if reduceOnly is not None:
            if isinstance(reduceOnly, bool):
                if reduceOnly:
                    params['reduceOnly'] = 'true'
                else:
                    params['reduceOnly'] = 'false'
            else:
                params['reduceOnly'] = str(reduceOnly)
        if attachAlgoOrds is not None:
            params['attachAlgoOrds'] = attachAlgoOrds
        data = self._request_with_params(POST, ORDER, params)

        return data

    def batch_orders(self, orders: Union[Order, Iterable[Order]]) -> Dict:
        orders_list = []
        if isinstance(orders, Order):
            orders_list.append(orders)
        else:
            orders_list = orders
        params = []

        for order in orders_list:
            param = {}
            if order.instId is not None:
                param['instId'] = str(order.instId)
            if order.tdMode is not None:
                param['tdMode'] = enum_to_str(order.tdMode)
            if order.ordType is not None:
                param['ordType'] = enum_to_str(order.ordType)
            if order.sz is not None:
                param['sz'] = str(abs(order.sz))
                if order.sz >= 0:
                    param['side'] = 'buy'
                else:
                    param['side'] = 'sell'
            if order.ccy is not None:
                param['ccy'] = enum_to_str(order.ccy)
            if order.clOrdId is not None:
                param['clOrdId'] = str(order.clOrdId)
            if order.tag is not None:
                param['tag'] = str(order.tag)
            if order.posSide is not None:
                param['posSide'] = enum_to_str(order.posSide)

            if order.reduceOnly is not None:
                if isinstance(order.reduceOnly, bool):
                    if order.reduceOnly:
                        param['reduceOnly'] = 'true'
                    else:
                        param['reduceOnly'] = 'false'
                else:
                    param['reduceOnly'] = str(order.reduceOnly)
            if order.tgtCcy is not None:
                param['tgtCcy'] = enum_to_str(order.tgtCcy)
            params.append(param)

        data = self._request_with_params(POST, BATCH_ORDERS, params)["data"]

        return data

    def cancel_order(self, instId: str, ordId: Optional[str] = None, clOrdId: Optional[str] = None):
        params = dict()

        if instId is not None:
            params['instId'] = str(instId)
        if ordId is not None:
            params['ordId'] = str(ordId)
        if clOrdId is not None:
            params['clOrdId'] = str(clOrdId)

        data = self._request_with_params(POST, CANCEL_ORDER, params)["data"]
        return data

    def cancel_batch_orders(self, orders: Union[CancelOrder, Iterable[CancelOrder]]):
        orders_list = []
        if isinstance(orders, Order):
            orders_list.append(orders)
        else:
            orders_list = orders
        params = []

        for order in orders_list:
            param = dict()
            if order.instId is not None:
                params['instId'] = str(order.instId)
            if order.ordId is not None:
                params['ordId'] = str(order.ordId)
            if order.clOrdId is not None:
                params['clOrdId'] = str(order.clOrdId)
            params.append(param)

        data = self._request_with_params(POST, CANCEL_BATCH_ORDERS, params)["data"]
        return data

    def get_order(self, instId: str, ordId: Optional[str] = None, clOrdId: Optional[str] = None) -> Dict:
        params = {}
        if instId is not None:
            params['instId'] = str(instId)
        if ordId is not None:
            params['ordId'] = str(ordId)
        if clOrdId is not None:
            params['clOrdId'] = str(clOrdId)

        data = self._request_with_params(GET, ORDER, params)["data"]

        return data

    # 市价全平
    def close_all_position(self,
                           instId: str,
                           posSide=None,
                           mgnMode: Union[MgnMode, str] = None,
                           ccy: Optional[Union[CcyType, str]] = None,
                           autoCxl=None
                           ):
        param = {}
        if instId is not None:
            param["instId"] = instId

        param["mgnMode"] = enum_to_str(mgnMode)
        data = self._request_with_params(POST, CLOSE_ALL_POSITION, param)
        return data

    # 修改订单
    def change_order(self,
                     insId: str,
                     ordId: str,  # 订单Id
                     clOrdId: str = None,  # 用户自定义id
                     reqId: str = None,  # 用户自定义修改事件ID
                     newSz: str = None,  # 修改的新数量
                     newPx: str = None,  # 修改后的新价格
                     newPxUsd: str = None,  # 以USD价格进行期权改单仅适用于期权，期权改单时，newPx/newPxUsd/newPxVol 只能填一个
                     newPxVol: str = None,  # 以隐含波动率进行期权改单，例如 1 代表 100%,仅适用于期权，期权改单时，newPx/newPxUsd/newPxVol 只能填一个
                     cslOnFail: bool = False,  # false: 不自动撤单
                     attachAlgoOrds=None
                     ) -> Dict:
        param = {}
        param["instId"] = insId
        param["cxlOnFail"] = cslOnFail
        if ordId is not None:
            param["ordId"] = ordId
        if clOrdId is not None:
            param["clOrdId"] = clOrdId
        if reqId is not None:
            param["reqId"] = reqId
        if newSz is not None:
            param["newSz"] = newSz
        if newPx is not None:
            param["newPx"] = newPx
        if newPxUsd is not None:
            param["newPxUsd"] = newPxUsd
        if newPxVol is not None:
            param["newPxVol"] = newPxVol
        if attachAlgoOrds is not None:
            param["attachAlgoOrds"] = attachAlgoOrds
        data = self._request_with_params(POST, AMEND_ORDER, param)

        return data

    # 策略订单,用于止盈止损
    def strategy_order(self, instId: str,
                       tdMode: Union[TdMode, str],
                       ordType: Union[OrderType, str],
                       sz: Union[float, int, str] = None,
                       ccy: Optional[Union[CcyType, str]] = None,
                       algoClOrdId: Optional[str] = None,
                       tag: Optional[str] = None,
                       side=None,
                       posSide: Optional[Union[PosSide, str]] = None,
                       tgtCcy: TrgCCY = None,
                       attachAlgoOrds=None,
                       closeFraction: str = None,  # 和sz只能填写一个
                       # 这些配置只有ordType为单向止盈止损时才会触发
                       tpTriggerPx: Optional[str] = None,  # 止盈触发价
                       tpOrdPx: Optional[str] = None,  # 止盈委托价,-1执行市价止盈
                       slTriggerPx: Optional[str] = None,  # 止损触发价
                       slOrdPx: Optional[str] = None,  # 止损委托价,-1 市价止盈
                       tpTriggerPxType: Optional[str] = None,  # 止盈触发价类型last：最新价格index：指数价格mark：标记价格默认为last
                       slTriggerPxType: Optional[str] = None,  # 止损触发价类型,last：最新价格.index：指数价格,mark：标记价格,默认为last
                       cxlOnClosePos: Optional[bool] = False,
                       # 决定用户所下的止盈止损订单是否与该交易产品对应的仓位关联。若关联，仓位被撤销时，该止盈止损订单会被同时撤销；若不关联，仓位被撤销时，该止盈止损订单不受影响。有效值：true：下单与仓位关联的止盈止损订单false：下单与仓位不关联的止盈止损订单默认值为false。若传入true，用户必须同时传入 reduceOnly = true，说明当下单与仓位关联的止盈止损订单时，必须为只减仓。适用于单币种保证金模式、跨币种保证金模式。
                       reduceOnly: bool = False,  # 只有true,closeFraction才是生效的
                       ) -> Dict:
        params = {}
        if instId is not None:
            params['instId'] = str(instId)
        if tdMode is not None:
            params['tdMode'] = enum_to_str(tdMode)
        if ordType is not None:
            params['ordType'] = enum_to_str(ordType)
        if sz is not None:
            params['sz'] = str(abs(sz))
            if sz >= 0:
                params['side'] = 'buy'
            else:
                params['side'] = 'sell'
        if ccy is not None:
            params['ccy'] = enum_to_str(ccy)
        if algoClOrdId is not None:
            params['algoClOrdId'] = str(algoClOrdId)
        if tag is not None:
            params['tag'] = str(tag)
        if posSide is not None:
            params['posSide'] = enum_to_str(posSide)
        if side is not None:
            params['side'] = str(side)
        if tgtCcy is not None:
            params['tgtCcy'] = tgtCcy
        if closeFraction is not None:
            params['closeFraction'] = closeFraction  # 目前只能为1
        if attachAlgoOrds is not None:
            params['attachAlgoOrds'] = attachAlgoOrds
        if tpTriggerPx is not None:
            params['tpTriggerPx'] = tpTriggerPx
        if tpOrdPx is not None:
            params['tpOrdPx'] = str(tpOrdPx)
        if slTriggerPx is not None:
            params['slTriggerPx'] = slTriggerPx
        if slOrdPx is not None:
            params['slOrdPx'] = str(slOrdPx)
        if tpTriggerPxType is not None:
            params['tpTriggerPxType'] = tpTriggerPxType
        if slTriggerPxType is not None:
            params['slTriggerPxType'] = slTriggerPxType
        if cxlOnClosePos is not None:
            params['cxlOnClosePos'] = cxlOnClosePos
        if reduceOnly is not None:
            params['reduceOnly'] = reduceOnly
        data = self._request_with_params(POST, ORDER_ALGO, params)

        return data

    # 修改策略订单
    def strategy_order_change(self, instId: str,
                              algoId: str,
                              newSz: str = None,
                              newTpTriggerPx='0',
                              newTpOrdPx="-1",
                              newSlTriggerPx='0',
                              newSlOrdPx="-1",
                              newTpTriggerPxType=None,
                              newSlTriggerPxType=None
                              ) -> Dict:
        params = {}
        if instId is not None:
            params['instId'] = str(instId)
        if algoId is not None:
            params['algoId'] = algoId
        if newSz is not None:
            params['newSz'] = newSz
        if newTpOrdPx is not None:
            params['newTpOrdPx'] = newTpOrdPx
        if newTpTriggerPx is not None:
            params['newTpTriggerPx'] = newTpTriggerPx
        if newSlTriggerPx is not None:
            params['newSlTriggerPx'] = newSlTriggerPx
        if newSlOrdPx is not None:
            params['newSlOrdPx'] = newSlOrdPx
        if newTpTriggerPxType is not None:
            params['newTpTriggerPxType'] = newTpTriggerPxType
        if newSlTriggerPxType is not None:
            params['newSlTriggerPxType'] = newSlTriggerPxType

        data = self._request_with_params(POST, AMEND_ALGO_CAHNGE, params)
        'Parameter newTpTriggerPxType error'
        return data
